package cave.renderer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.xmlpull.v1.XmlPullParser;

import cave.game.MainActivity;
import cave.ui.Font;
import cave.utils.OrderedArray;

import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

public class TextureManager {
	
	public class Atlas {

		public static final int WRAP_REPEAT = 0;
		public static final int WRAP_CLAMP = 1;

		public static final int FILTER_LINEAR = 0;
		public static final int FILTER_POINT = 1;
		
		private int id;
		private String name;
		private boolean loaded;
		private int users;		
		private int wrapMode;
		private int filterMode;
		
		public Atlas(String name, int wrapMode, int filterMode) {
			this.name = name;
			this.wrapMode = wrapMode;
			this.filterMode = filterMode;
		}
		
		public int getId() { return id; }
		public void setId(int id) { this.id = id; }
		
		public String getName() { return name; }
		
		public boolean isLoaded() { return loaded; }
		public void setLoaded(boolean flag) { loaded = flag; }
		
		public int getUserCount() { return users; }
		public void incUsers() { users++; }
		public void decUsers() { users--; }
		
		public int getWrapMode() { return wrapMode; }
		public int getFilterMode() { return filterMode; }
		
	}
	
	private RenderMsgQueue msgQueue;
	
	// Android resources object
	private Resources resources;
	
	private Map<String, Atlas> atlases;
	private OrderedArray<Object> loadedAtlases;
	
	private Map<String, Texture> textures;
	private Map<String, Animation> animations;
	private Map<String, Font> fonts;
	
	public TextureManager(Resources resources, RenderMsgQueue msgQueue) {
		this.resources = resources;
		this.msgQueue = msgQueue;
		atlases = new HashMap<String, Atlas>();
		loadedAtlases = new OrderedArray<Object>();
		textures = new HashMap<String, Texture>();
		animations = new HashMap<String, Animation>();
		fonts = new HashMap<String, Font>();
		
		loadTextureConfig();
		loadAnimationConfig();
		loadFontConfig();
	}
	
	public Texture getTexture(String id) {
		Texture texture = textures.get(id);
		if (texture == null) {
			Log.w(MainActivity.TAG, "TextureManager.getTexture: texture id \"" + id + "\" not found");
			return null;
		}
		
		if (!texture.getAtlas().isLoaded()) {
			loadAtlas(texture.getAtlas());
		}
		
		return texture;
	}
	
	public Animation getAnimation(String id) {
		Animation animation = animations.get(id);
		if (animation == null) {
			Log.w(MainActivity.TAG, "TextureManager.getAnimation: animation id \"" + id + "\" not found");
			return null;
		}
		
		Atlas atlas = animation.getFrames()[0].getAtlas();
		if (!atlas.isLoaded()) {
			loadAtlas(atlas);
		}

		return animation;
	}
	
	public Font getFont(String id) {
		Font font = fonts.get(id);
		if (font == null) {
			Log.w(MainActivity.TAG, "TextureManager.getFont: font id \"" + id + "\" not found");
			return null;
		}
		
		return font;
	}

	private void loadTextureConfig() {
		int resId = resources.getIdentifier("textures", "xml", MainActivity.PACKAGE_NAME);
		XmlResourceParser p = resources.getXml(resId);
		
		Atlas curAtlas = null;
		
		try {
			int eventType = p.getEventType();
			while(eventType != XmlPullParser.END_DOCUMENT) {
				
				if(eventType == XmlPullParser.START_TAG) {
					String name = p.getName();
					
					if (name.equals("atlas")) {
						
						String atlasName = p.getAttributeValue(null, "src");
						int wrapMode = p.getAttributeIntValue(null, "wrapMode", 0);
						int filterMode = p.getAttributeIntValue(null, "filterMode", 0);
						curAtlas = atlases.get(atlasName);
						if (curAtlas == null) {
							curAtlas = new Atlas(atlasName, wrapMode, filterMode);
							atlases.put(atlasName, curAtlas);
						}
						
					} else if (name.equals("texture")) {
						
						// We're inside <atlas>
						if (curAtlas != null) {
							
							String id = p.getAttributeValue(null, "id");
							float left = p.getAttributeFloatValue(null, "left", 0.0f);
							float bottom = p.getAttributeFloatValue(null, "bottom", 0.0f);
							float width = p.getAttributeFloatValue(null, "width", 1.0f);
							float height = p.getAttributeFloatValue(null, "height", 1.0f);
							
							textures.put(id, new Texture(left, bottom, width, height, curAtlas));
							
						} else {
							Log.w(MainActivity.TAG, "TextureManager.loadTextureConfig: malformed xml");
						}
					}	
				} else if (eventType == XmlPullParser.END_TAG) {
					String name = p.getName();
					
					if (name.equals("atlas")) {
						curAtlas = null;
					}
				}
				
				eventType = p.next();
			}
		} catch (Exception e) {
			Log.e(MainActivity.TAG, "TextureManager.loadTextureConfig: XmlPullParser error: " + e.getMessage());
		}
		
	}
	
	private void loadAnimationConfig() {
		int resId = resources.getIdentifier("animations", "xml", MainActivity.PACKAGE_NAME);
		XmlResourceParser p = resources.getXml(resId);
		
		Atlas curAtlas = null;
		Animation curAnimation = null;
		String curAnimationName = "";
		ArrayList<Texture> curFrames = new ArrayList<Texture>();
		
		try {
			int eventType = p.getEventType();
			while(eventType != XmlPullParser.END_DOCUMENT) {
				
				if(eventType == XmlPullParser.START_TAG) {
					String name = p.getName();
					
					if (name.equals("animation")) {
						
						curAnimation = new Animation();
						curAnimationName = p.getAttributeValue(null, "id");
						curAnimation.setFps(p.getAttributeFloatValue(null, "fps", 1.0f));
						
						String atlasName = p.getAttributeValue(null, "src");
						int wrapMode = p.getAttributeIntValue(null, "wrapMode", 0);
						int filterMode = p.getAttributeIntValue(null, "filterMode", 0);
						curAtlas = atlases.get(atlasName);
						if (curAtlas == null) {
							curAtlas = new Atlas(atlasName, wrapMode, filterMode);
							atlases.put(atlasName, curAtlas);
						}

					} else if (name.equals("frame")) {
						
						// We're inside <animation>
						if (curAnimation != null) {
							
							float left = p.getAttributeFloatValue(null, "left", 0.0f);
							float bottom = p.getAttributeFloatValue(null, "bottom", 0.0f);
							float width = p.getAttributeFloatValue(null, "width", 1.0f);
							float height = p.getAttributeFloatValue(null, "height", 1.0f);
							
							curFrames.add(new Texture(left, bottom, width, height, curAtlas));
						} else {
							Log.w(MainActivity.TAG, "TextureManager.loadAnimationConfig: malformed xml");
						}
					}
					
				} else if (eventType == XmlPullParser.END_TAG) {
					String name = p.getName();
					
					if (name.equals("animation")) {
						if (curAnimation != null) {
							Texture[] frames = new Texture[curFrames.size()];
							curFrames.toArray(frames);
							curAnimation.setFrames(frames);
							
							animations.put(curAnimationName, curAnimation);
							
							curAnimation = null;
							curAtlas = null;
						} else {
							Log.w(MainActivity.TAG, "TextureManager.loadAnimationConfig: malformed xml");
						}
					}
				}
				
				eventType = p.next();
			}
		} catch (Exception e) {
			Log.e(MainActivity.TAG, "TextureManager.loadAnimationConfig: XmlPullParser error: " + e.getMessage());
		}
	}
	
	private void loadFontConfig() {
		int resId = resources.getIdentifier("fonts", "xml", MainActivity.PACKAGE_NAME);
		XmlResourceParser p = resources.getXml(resId);
		
		try {
			int eventType = p.getEventType();
			while(eventType != XmlPullParser.END_DOCUMENT) {
				
				if(eventType == XmlPullParser.START_TAG) {
					String name = p.getName();
					
					if (name.equals("font")) {
						
						String id = p.getAttributeValue(null, "id");
						String characters = p.getAttributeValue(null, "characters");
						float charWidth = p.getAttributeFloatValue(null, "charWidth", 0.0f);
						float charHeight = p.getAttributeFloatValue(null, "charHeight", 0.0f);
						float charAspectRatio = p.getAttributeFloatValue(null, "charApratio", 0.0f);
						int charsOnRow = p.getAttributeIntValue(null, "charsOnRow", 0);
						
						Log.d(MainActivity.TAG, "TextureManager.loadFontConfig: chars: " + characters);
						
						String atlasName = p.getAttributeValue(null, "src");
						Atlas atlas = atlases.get(atlasName);
						if (atlas == null) {
							atlas = new Atlas(atlasName, Atlas.WRAP_CLAMP, Atlas.FILTER_POINT);
							atlases.put(atlasName, atlas);
						}
						
						if (!atlas.isLoaded()) {
							loadAtlas(atlas);
						}
						
						fonts.put(id, new Font(charWidth, charHeight, charAspectRatio, charsOnRow, atlas, characters));
					}
				}
				
				eventType = p.next();
			}
		} catch (Exception e) {
			Log.e(MainActivity.TAG, "TextureManager.loadFontConfig: XmlPullParser error: " + e.getMessage());
		}
	}
	
	private void loadAtlas(Atlas atlas) {

		String[] nameParts = atlas.getName().split("\\.");
		int resId = resources.getIdentifier(nameParts[0], "drawable", MainActivity.PACKAGE_NAME);		
		if (resId == 0) {
			Log.w(MainActivity.TAG, "TextureManager.loadAtlas: atlas \"" + atlas.getName() + "\" not found");
			return;
		}
		
		int id = loadedAtlases.add(new Object());		
		atlas.setId(id);
		
		Log.d(MainActivity.TAG, "TextureManager.loadAtlas: loading atlas \"" + atlas.getName()  + " \" with id " + id);
		
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inScaled = false;
		Bitmap bitmap = BitmapFactory.decodeResource(resources, resId, options);
		
		int width = bitmap.getWidth();
		int height = bitmap.getHeight();
		int[] pixels = new int[width * height];
		bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
		bitmap.recycle();
		
		for (int i=0;i<pixels.length;i+=1) {
		    int argb = pixels[i];
		    pixels[i] = argb&0xff00ff00 | ((argb&0xff)<<16) | ((argb>>16)&0xff);
		}
		
		msgQueue.addParamWithObject(RenderMsgQueue.MSG_CREATETEXTURE, pixels);
		msgQueue.addParam(width);
		msgQueue.addParam(height);
		msgQueue.addParam(atlas.getWrapMode());
		msgQueue.addParam(atlas.getFilterMode());
		
		atlas.setLoaded(true);
	}
	
	public void clean() {
		for (Entry<String, Atlas> entry : atlases.entrySet()) {
			
			Atlas atlas = entry.getValue();
			if (atlas.isLoaded() && atlas.getUserCount() <= 0) {
				
				Log.d(MainActivity.TAG, "TextureManager.clean: cleaning atlas \"" + atlas.getName()  + "\" (id " + atlas.getId() + ")");
				
				loadedAtlases.remove(atlas.getId());
				atlas.setLoaded(false);
				
				msgQueue.addParam(RenderMsgQueue.MSG_DESTROYTEXTURE);
				msgQueue.addParam(atlas.getId());
			}
		}
	}
	
	public void reloadAtlases() {
		loadedAtlases.clear();
		for (Entry<String, Atlas> entry : atlases.entrySet()) {
			Atlas atlas = entry.getValue();
			if (atlas.isLoaded()) {
				loadAtlas(atlas);
			}
		}
	}
	
}
